/** @file
  Implementation of the HII for the Opal UEFI Driver.

Copyright (c) 2016, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "OpalHii.h"
#include "OpalDriver.h"
#include "OpalHiiPrivate.h"

//
// This is the generated IFR binary Data for each formset defined in VFR.
// This Data array is ready to be used as input of HiiAddPackages() to
// create a packagelist (which contains Form packages, String packages, etc).
//
extern UINT8  OpalPasswordFormBin[];

//
// This is the generated String package Data for all .UNI files.
// This Data array is ready to be used as input of HiiAddPackages() to
// create a packagelist (which contains Form packages, String packages, etc).
//
extern UINT8  OpalPasswordDxeStrings[];

CHAR16  OpalPasswordStorageName[] = L"OpalHiiConfig";

EFI_HII_CONFIG_ACCESS_PROTOCOL gHiiConfigAccessProtocol;

//
// Handle to the list of HII packages (forms and strings) for this driver
//
EFI_HII_HANDLE gHiiPackageListHandle = NULL;

//
// Package List GUID containing all form and string packages
//
const EFI_GUID gHiiPackageListGuid = PACKAGE_LIST_GUID;
const EFI_GUID gHiiSetupVariableGuid = SETUP_VARIABLE_GUID;

//
// Structure that contains state of the HII
// This structure is updated by Hii.cpp and its contents
// is rendered in the HII.
//
OPAL_HII_CONFIGURATION gHiiConfiguration;

CHAR8 gHiiOldPassword[MAX_PASSWORD_CHARACTER_LENGTH] = {0};
UINT32 gHiiOldPasswordLength = 0;

//
// The device path containing the VENDOR_DEVICE_PATH and EFI_DEVICE_PATH_PROTOCOL
//
HII_VENDOR_DEVICE_PATH gHiiVendorDevicePath = {
    {
        {
            HARDWARE_DEVICE_PATH,
            HW_VENDOR_DP,
            {
                (UINT8)(sizeof(VENDOR_DEVICE_PATH)),
                (UINT8)((sizeof(VENDOR_DEVICE_PATH)) >> 8)
            }
        },
        OPAL_PASSWORD_CONFIG_GUID
    },
    {
        END_DEVICE_PATH_TYPE,
        END_ENTIRE_DEVICE_PATH_SUBTYPE,
        {
            (UINT8)(END_DEVICE_PATH_LENGTH),
            (UINT8)((END_DEVICE_PATH_LENGTH) >> 8)
        }
    }
};


/**
  Sets the current system state of global config variables.

**/
VOID
HiiSetCurrentConfiguration(
  VOID
  )
{
  EFI_STATUS                            Status;
  OPAL_EXTRA_INFO_VAR                   OpalExtraInfo;
  UINTN                                 DataSize;

  gHiiConfiguration.NumDisks = GetDeviceCount();

  DataSize = sizeof (OPAL_EXTRA_INFO_VAR);
  Status = gRT->GetVariable (
                  OPAL_EXTRA_INFO_VAR_NAME,
                  &gOpalExtraInfoVariableGuid,
                  NULL,
                  &DataSize,
                  &OpalExtraInfo
                  );
  if (!EFI_ERROR (Status)) {
    gHiiConfiguration.EnableBlockSid = OpalExtraInfo.EnableBlockSid;
  }
}

/**
  Install the HII related resources.

  @retval  EFI_SUCCESS        Install all the resources success.
  @retval  other              Error occur when install the resources.
**/
EFI_STATUS
HiiInstall(
  VOID
  )
{
  EFI_STATUS                   Status;
  EFI_HANDLE                   DriverHandle;

  //
  // Clear the global configuration.
  //
  ZeroMem(&gHiiConfiguration, sizeof(gHiiConfiguration));

  //
  // Obtain the driver handle that the BIOS assigned us
  //
  DriverHandle = HiiGetDriverImageHandleCB();

  //
  // Populate the config access protocol with the three functions we are publishing
  //
  gHiiConfigAccessProtocol.ExtractConfig = ExtractConfig;
  gHiiConfigAccessProtocol.RouteConfig = RouteConfig;
  gHiiConfigAccessProtocol.Callback = DriverCallback;

  //
  // Associate the required protocols with our driver handle
  //
  Status = gBS->InstallMultipleProtocolInterfaces(
               &DriverHandle,
               &gEfiHiiConfigAccessProtocolGuid,
               &gHiiConfigAccessProtocol,      // HII callback
               &gEfiDevicePathProtocolGuid,
               &gHiiVendorDevicePath,        // required for HII callback allow all disks to be shown in same hii
               NULL
           );

  if (EFI_ERROR(Status)) {
    return Status;
  }

  return OpalHiiAddPackages();
}

/**
  Install the HII form and string packages.

  @retval  EFI_SUCCESS           Install all the resources success.
  @retval  EFI_OUT_OF_RESOURCES  Out of resource error.
**/
EFI_STATUS
OpalHiiAddPackages(
  VOID
  )
{
  EFI_HANDLE                   DriverHandle;
  CHAR16                       *NewString;

  DriverHandle = HiiGetDriverImageHandleCB();

  //
  // Publish the HII form and HII string packages
  //
  gHiiPackageListHandle = HiiAddPackages(
                                &gHiiPackageListGuid,
                                DriverHandle,
                                OpalPasswordDxeStrings,
                                OpalPasswordFormBin,
                                (VOID*)NULL
                                );

  //
  // Make sure the packages installed successfully
  //
  if (gHiiPackageListHandle == NULL) {
    DEBUG ((DEBUG_INFO, "OpalHiiAddPackages failed\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Update Version String in main window
  //
  NewString = HiiGetDriverNameCB ();
  if (HiiSetString(gHiiPackageListHandle, STRING_TOKEN(STR_MAIN_OPAL_VERSION), NewString, NULL) == 0) {
    DEBUG ((DEBUG_INFO,  "OpalHiiAddPackages: HiiSetString( ) failed\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

/**
  Uninstall the HII capability.

  @retval  EFI_SUCCESS           Uninstall all the resources success.
  @retval  others                Other errors occur when unistall the hii resource.
**/
EFI_STATUS
HiiUninstall(
  VOID
  )
{
  EFI_STATUS                   Status;

  //
  // Remove the packages we've provided to the BIOS
  //
  HiiRemovePackages(gHiiPackageListHandle);

  //
  // Remove the protocols from our driver handle
  //
  Status = gBS->UninstallMultipleProtocolInterfaces(
                          HiiGetDriverImageHandleCB(),
                          &gEfiHiiConfigAccessProtocolGuid,
                          &gHiiConfigAccessProtocol,        // HII callback
                          &gEfiDevicePathProtocolGuid,
                          &gHiiVendorDevicePath,            // required for HII callback
                          NULL
                      );
  if (EFI_ERROR(Status)) {
    DEBUG ((DEBUG_INFO, "Cannot uninstall Hii Protocols: %r\n", Status));
  }

  return Status;
}

/**
  Updates the main menu form.

  @retval  EFI_SUCCESS           update the main form success.
**/
EFI_STATUS
HiiPopulateMainMenuForm (
  VOID
  )
{
  UINT8         Index;
  CHAR8         *DiskName;
  EFI_STRING_ID DiskNameId;
  OPAL_DISK     *OpalDisk;

  HiiSetCurrentConfiguration();

  gHiiConfiguration.SupportedDisks = 0;

  for (Index = 0; Index < gHiiConfiguration.NumDisks; Index++) {
    OpalDisk = HiiGetOpalDiskCB (Index);
    if ((OpalDisk != NULL) && OpalFeatureSupported (&OpalDisk->SupportedAttributes)) {
      gHiiConfiguration.SupportedDisks |= (1 << Index);
      DiskNameId = GetDiskNameStringId (Index);
      DiskName = HiiDiskGetNameCB (Index);
      if ((DiskName == NULL) || (DiskNameId == 0)) {
        return EFI_UNSUPPORTED;
      }
      HiiSetFormString(DiskNameId, DiskName);
    }
  }

  OpalHiiSetBrowserData ();
  return EFI_SUCCESS;
}

/**
  Update the disk action info.

  @param     ActionString
  @param     SelectedAction

  @retval  EFI_SUCCESS           Uninstall all the resources success.
**/
EFI_STATUS
HiiSelectDiskAction (
  CHAR8           *ActionString,
  UINT8           SelectedAction
  )
{
  OPAL_DISK                     *OpalDisk;
  OPAL_DISK_ACTIONS              AvailActions;

  OpalHiiGetBrowserData ();

  HiiSetFormString(STRING_TOKEN(STR_DISK_ACTION_LBL), ActionString);
  HiiSetFormString(STRING_TOKEN(STR_ACTION_STATUS), " ");

  gHiiConfiguration.SelectedAction = SelectedAction;
  gHiiConfiguration.AvailableFields = 0;

  OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
  if (OpalDisk == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (OpalSupportGetAvailableActions (&OpalDisk->SupportedAttributes, &OpalDisk->LockingFeature, OpalDisk->Owner, &AvailActions) != TcgResultSuccess) {
    return EFI_DEVICE_ERROR;
  }

  switch (SelectedAction) {
    case HII_KEY_ID_GOTO_LOCK:
    case HII_KEY_ID_GOTO_UNLOCK:
    case HII_KEY_ID_GOTO_SET_ADMIN_PWD:
    case HII_KEY_ID_GOTO_SET_USER_PWD:
    case HII_KEY_ID_GOTO_SECURE_ERASE:
    case HII_KEY_ID_GOTO_DISABLE_USER:
    case HII_KEY_ID_GOTO_ENABLE_FEATURE:   // User is required to enter Password to enable Feature
      gHiiConfiguration.AvailableFields |= HII_FIELD_PASSWORD;
      break;

    case HII_KEY_ID_GOTO_PSID_REVERT:
      gHiiConfiguration.AvailableFields |= HII_FIELD_PSID;
      break;

    case HII_KEY_ID_GOTO_REVERT:
      gHiiConfiguration.AvailableFields |= HII_FIELD_PASSWORD;
      if (OpalDisk->SupportedAttributes.PyriteSsc != 1) {
        //
        // According to current Pyrite SSC Spec 1.00, there is no parameter for RevertSP method.
        // So issue RevertSP method without any parameter by suppress KeepUserData option.
        //
        gHiiConfiguration.AvailableFields |= HII_FIELD_KEEP_USER_DATA;
      }
      if (AvailActions.RevertKeepDataForced) {
        gHiiConfiguration.AvailableFields |= HII_FIELD_KEEP_USER_DATA_FORCED;
      }
      break;
  }

  OpalHiiSetBrowserData ();

  return EFI_SUCCESS;
}

/**
  Get disk name string id.

  @param   DiskIndex             The input disk index info.

  @retval  The disk name string id.

**/
EFI_STRING_ID
GetDiskNameStringId(
  UINT8 DiskIndex
  )
{
  switch (DiskIndex) {
    case 0: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_0);
    case 1: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_1);
    case 2: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_2);
    case 3: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_3);
    case 4: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_4);
    case 5: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_5);
  }
  return 0;
}

/**
  This function processes the results of changes in configuration.

  @param  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Action                 Specifies the type of action taken by the browser.
  @param  QuestionId             A unique value which is sent to the original
                                 exporting driver so that it can identify the type
                                 of data to expect.
  @param  Type                   The type of value for the question.
  @param  Value                  A pointer to the data being sent to the original
                                 exporting driver.
  @param  ActionRequest          On return, points to the action requested by the
                                 callback function.

  @retval EFI_SUCCESS            The callback successfully handled the action.
  @retval EFI_OUT_OF_RESOURCES   Not enough storage is available to hold the
                                 variable and its data.
  @retval EFI_DEVICE_ERROR       The variable could not be saved.
  @retval EFI_UNSUPPORTED        The specified Action is not supported by the
                                 callback.

**/
EFI_STATUS
EFIAPI
DriverCallback(
  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL    *This,
  EFI_BROWSER_ACTION                      Action,
  EFI_QUESTION_ID                         QuestionId,
  UINT8                                   Type,
  EFI_IFR_TYPE_VALUE                      *Value,
  EFI_BROWSER_ACTION_REQUEST              *ActionRequest
  )
{
  HII_KEY    HiiKey;
  UINT8      HiiKeyId;

  if (ActionRequest != NULL) {
    *ActionRequest = EFI_BROWSER_ACTION_REQUEST_NONE;
  } else {
    return EFI_INVALID_PARAMETER;
  }

  //
  // If QuestionId is an auto-generated key (label, empty line, etc.), ignore it.
  //
  if ((QuestionId & HII_KEY_FLAG) == 0) {
    return EFI_SUCCESS;
  }

  HiiKey.Raw = QuestionId;
  HiiKeyId   = (UINT8) HiiKey.KeyBits.Id;

  if (Action == EFI_BROWSER_ACTION_FORM_OPEN) {
    switch (HiiKeyId) {
      case HII_KEY_ID_VAR_SUPPORTED_DISKS:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_VAR_SUPPORTED_DISKS\n"));
        return HiiPopulateMainMenuForm ();

      case HII_KEY_ID_VAR_SELECTED_DISK_AVAILABLE_ACTIONS:
        return HiiPopulateDiskInfoForm();
    }
  } else if (Action == EFI_BROWSER_ACTION_CHANGING) {
    switch (HiiKeyId) {
      case HII_KEY_ID_GOTO_DISK_INFO:
        return HiiSelectDisk((UINT8)HiiKey.KeyBits.Index);

      case HII_KEY_ID_GOTO_LOCK:
        return HiiSelectDiskAction("Action: Lock", HiiKeyId);

      case HII_KEY_ID_GOTO_UNLOCK:
        return HiiSelectDiskAction("Action: Unlock", HiiKeyId);

      case HII_KEY_ID_GOTO_SET_ADMIN_PWD:
        return HiiSelectDiskAction("Action: Set Administrator Password", HiiKeyId);

      case HII_KEY_ID_GOTO_SET_USER_PWD:
        return HiiSelectDiskAction("Action: Set User Password", HiiKeyId);

      case HII_KEY_ID_GOTO_SECURE_ERASE:
        return HiiSelectDiskAction("Action: Secure Erase", HiiKeyId);

      case HII_KEY_ID_GOTO_PSID_REVERT:
        return HiiSelectDiskAction("Action: Revert to Factory Defaults with PSID", HiiKeyId);

      case HII_KEY_ID_GOTO_REVERT:
        return HiiSelectDiskAction("Action: Revert to Factory Defaults", HiiKeyId);

      case HII_KEY_ID_GOTO_DISABLE_USER:
        return HiiSelectDiskAction("Action: Disable User", HiiKeyId);

      case HII_KEY_ID_GOTO_ENABLE_FEATURE:
        return HiiSelectDiskAction("Action: Enable Feature", HiiKeyId);

      case HII_KEY_ID_ENTER_PASSWORD:
        return HiiPasswordEntered(Value->string);
    }
  } else if (Action == EFI_BROWSER_ACTION_CHANGED) {
    switch (HiiKeyId) {
      case HII_KEY_ID_ENTER_PSID:
        HiiPsidRevert();
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;

      case HII_KEY_ID_BLOCKSID:
        HiiSetBlockSid(Value->b);
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;
    }
  }

  return EFI_UNSUPPORTED;
}

/**
  Update the global Disk index info.

  @param   Index             The input disk index info.

  @retval  EFI_SUCCESS       Update the disk index info success.

**/
EFI_STATUS
HiiSelectDisk(
  UINT8 Index
  )
{
  OpalHiiGetBrowserData();
  gHiiConfiguration.SelectedDiskIndex = Index;
  OpalHiiSetBrowserData ();

  return EFI_SUCCESS;
}

/**
  Draws the disk info form.

  @retval  EFI_SUCCESS       Draw the disk info success.

**/
EFI_STATUS
HiiPopulateDiskInfoForm(
  VOID
  )
{
  OPAL_DISK*                    OpalDisk;
  OPAL_DISK_ACTIONS             AvailActions;
  TCG_RESULT                    Ret;
  CHAR8                         *DiskName;

  OpalHiiGetBrowserData();

  DiskName = HiiDiskGetNameCB (gHiiConfiguration.SelectedDiskIndex);
  if (DiskName == NULL) {
    return EFI_UNSUPPORTED;
  }
  HiiSetFormString(STRING_TOKEN(STR_DISK_INFO_SELECTED_DISK_NAME), DiskName);

  ZeroMem(gHiiConfiguration.Psid, sizeof(gHiiConfiguration.Psid));

  gHiiConfiguration.SelectedDiskAvailableActions = HII_ACTION_NONE;

  OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);

  if (OpalDisk != NULL) {
    OpalDiskUpdateStatus (OpalDisk);
    Ret = OpalSupportGetAvailableActions(&OpalDisk->SupportedAttributes, &OpalDisk->LockingFeature, OpalDisk->Owner, &AvailActions);
    if (Ret == TcgResultSuccess) {
      //
      // Update actions, always allow PSID Revert
      //
      gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.PsidRevert == 1) ? HII_ACTION_PSID_REVERT : HII_ACTION_NONE;

      //
      // Always allow unlock to handle device migration
      //
      gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.Unlock == 1) ? HII_ACTION_UNLOCK : HII_ACTION_NONE;

      if (!OpalFeatureEnabled (&OpalDisk->SupportedAttributes, &OpalDisk->LockingFeature)) {
        if (OpalDisk->Owner == OpalOwnershipNobody) {
          gHiiConfiguration.SelectedDiskAvailableActions |= HII_ACTION_ENABLE_FEATURE;

          //
          // Update strings
          //
          HiiSetFormString( STRING_TOKEN(STR_DISK_INFO_PSID_REVERT), "PSID Revert to factory default");
        } else {
          DEBUG ((DEBUG_INFO, "Feature disabled but ownership != nobody\n"));
        }
      } else {
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.Revert == 1) ? HII_ACTION_REVERT : HII_ACTION_NONE;
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.AdminPass == 1) ? HII_ACTION_SET_ADMIN_PWD : HII_ACTION_NONE;
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.UserPass == 1) ? HII_ACTION_SET_USER_PWD : HII_ACTION_NONE;
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.SecureErase == 1) ? HII_ACTION_SECURE_ERASE : HII_ACTION_NONE;
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.DisableUser == 1) ? HII_ACTION_DISABLE_USER : HII_ACTION_NONE;

        HiiSetFormString (STRING_TOKEN(STR_DISK_INFO_PSID_REVERT), "PSID Revert to factory default and Disable");

        //
        // Determine revert options for disk
        // Default initialize keep user Data to be true
        //
        gHiiConfiguration.KeepUserData = 1;
        if (OpalDisk->SupportedAttributes.PyriteSsc == 1) {
          //
          // According to current Pyrite SSC Spec 1.00, there is no parameter for RevertSP method.
          // So issue RevertSP method without any parameter by set default value to FALSE.
          //
          gHiiConfiguration.KeepUserData = 0;
        }

      }
    }
  }

  //
  // Pass the current configuration to the BIOS
  //
  OpalHiiSetBrowserData ();

  return EFI_SUCCESS;
}

/**
  Reverts the Opal disk to factory default.

  @retval  EFI_SUCCESS       Do the required action success.

**/
EFI_STATUS
HiiPsidRevert(
  VOID
  )
{
  CHAR8                         Response[DEFAULT_RESPONSE_SIZE];
  TCG_PSID                      Psid;
  OPAL_DISK                     *OpalDisk;
  TCG_RESULT                    Ret;
  OPAL_SESSION                  Session;

  Ret = TcgResultFailure;

  OpalHiiGetBrowserData();

  UnicodeStrToAsciiStr(gHiiConfiguration.Psid, (CHAR8*)Psid.Psid);

  OpalDisk = HiiGetOpalDiskCB (gHiiConfiguration.SelectedDiskIndex);
  if (OpalDisk != NULL) {
    ZeroMem(&Session, sizeof(Session));
    Session.Sscp = OpalDisk->Sscp;
    Session.MediaId = OpalDisk->MediaId;
    Session.OpalBaseComId = OpalDisk->OpalBaseComId;

    Ret = OpalSupportPsidRevert(&Session, Psid.Psid, (UINT32)sizeof(Psid.Psid), OpalDisk->OpalDevicePath);
  }

  if (Ret == TcgResultSuccess) {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "PSID Revert: Success" );
  } else {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "PSID Revert: Failure" );
  }

  HiiSetFormString(STRING_TOKEN(STR_ACTION_STATUS), Response);

  return EFI_SUCCESS;
}

/**
  Set password for the disk.

  @param      OpalDisk       The disk need to set the password.
  @param      Password       The input password.
  @param      PassLength     The input password length.

  @retval  EFI_SUCCESS       Do the required action success.

**/
EFI_STATUS
HiiSetPassword(
  OPAL_DISK          *OpalDisk,
  VOID               *Password,
  UINT32             PassLength
  )
{
  CHAR8                         Response[DEFAULT_RESPONSE_SIZE];
  TCG_RESULT                    Ret;
  BOOLEAN                       ExistingPassword;
  OPAL_SESSION                  Session;

  ExistingPassword = FALSE;

  //
  // PassLength = 0 means check whether exist old password.
  //
  if (PassLength == 0) {
    ZeroMem(gHiiOldPassword, sizeof(gHiiOldPassword));
    gHiiOldPasswordLength = 0;

    if (gHiiConfiguration.SelectedAction == HII_KEY_ID_GOTO_ENABLE_FEATURE) {
      ExistingPassword = FALSE;
    } else if (gHiiConfiguration.SelectedAction == HII_KEY_ID_GOTO_SET_ADMIN_PWD) {
      ExistingPassword = OpalUtilAdminPasswordExists(OpalDisk->Owner, &OpalDisk->LockingFeature);
    } else if (gHiiConfiguration.SelectedAction == HII_KEY_ID_GOTO_SET_USER_PWD) {
      //
      // Set user Password option shall only be shown if an Admin Password exists
      // so a Password is always required (Admin or Existing User Password)
      //
      ExistingPassword = TRUE;
    }

    //
    // Return error if there is a previous Password
    // see UEFI 2.4 errata B, Figure 121. Password Flowchart
    //
    return ExistingPassword ? EFI_DEVICE_ERROR : EFI_SUCCESS;
  }

  ZeroMem(&Session, sizeof(Session));
  Session.Sscp = OpalDisk->Sscp;
  Session.MediaId = OpalDisk->MediaId;
  Session.OpalBaseComId = OpalDisk->OpalBaseComId;

  AsciiSPrint(Response, DEFAULT_RESPONSE_SIZE, "%a", "Set Password: Failure");
  //
  // Password entered.
  // No current Owner, so set new Password, must be admin Password
  //
  if (OpalDisk->Owner == OpalOwnershipNobody) {
    Ret = OpalSupportEnableOpalFeature (&Session, OpalDisk->Msid, OpalDisk->MsidLength,Password, PassLength, OpalDisk->OpalDevicePath);
    if (Ret == TcgResultSuccess) {
      AsciiSPrint(Response, DEFAULT_RESPONSE_SIZE, "%a", "Set Password: Success");
    }

    HiiSetFormString(STRING_TOKEN(STR_ACTION_STATUS), Response);
    return EFI_SUCCESS;
  }

  //
  // 1st Password entered
  //
  if (OpalDisk->Owner == OpalOwnershipUnknown && gHiiOldPasswordLength == 0) {

    //
    // Unknown ownership - prompt for old Password, then new
    // old Password is not set yet - first time through
    // assume authority provided is admin1, overwritten if user1 authority works below
    //
    if (gHiiConfiguration.SelectedAction == HII_KEY_ID_GOTO_SET_USER_PWD) {
      //
      // First try to login as USER1 to Locking SP to see if we're simply updating its Password
      //
      Ret =  OpalUtilVerifyPassword (&Session, Password, PassLength, OPAL_LOCKING_SP_USER1_AUTHORITY);
      if (Ret == TcgResultSuccess) {
        //
        // User1 worked so authority 1 means user 1
        //
        CopyMem(gHiiOldPassword, Password, PassLength);
        gHiiOldPasswordLength = PassLength;

        return EFI_SUCCESS;
      }
    }

    //
    // Else try admin1 below
    //
    Ret =  OpalUtilVerifyPassword (&Session, Password, PassLength, OPAL_LOCKING_SP_ADMIN1_AUTHORITY);
    if (Ret == TcgResultSuccess) {
      CopyMem(gHiiOldPassword, Password, PassLength);
      gHiiOldPasswordLength = PassLength;

      return EFI_SUCCESS;
    } else {
      DEBUG ((DEBUG_INFO, "start session with old PW failed - return EFI_NOT_READY - mistyped old PW\n"));
      HiiSetFormString(STRING_TOKEN(STR_ACTION_STATUS), "Authentication Failure");

      ZeroMem(gHiiOldPassword, sizeof(gHiiOldPassword));
      gHiiOldPasswordLength = 0;

      return EFI_NOT_READY;
    }
  }

  //
  // New Password entered
  //
  if (gHiiConfiguration.SelectedAction == HII_KEY_ID_GOTO_SET_USER_PWD) {
    Ret = OpalSupportSetPassword(
                            &Session,
                            gHiiOldPassword,
                            gHiiOldPasswordLength,
                            Password,
                            PassLength,
                            OpalDisk->OpalDevicePath,
                            FALSE
                            );
  } else {
    Ret = OpalSupportSetPassword(
                            &Session,
                            gHiiOldPassword,
                            gHiiOldPasswordLength,
                            Password,
                            PassLength,
                            OpalDisk->OpalDevicePath,
                            TRUE
                            );
  }

  if (Ret == TcgResultSuccess) {
    AsciiSPrint(Response, DEFAULT_RESPONSE_SIZE, "%a", "Set Password: Success");
  }

  //
  // Reset old Password storage
  //
  ZeroMem(gHiiOldPassword, sizeof(gHiiOldPassword));
  gHiiOldPasswordLength = 0;

  HiiSetFormString(STRING_TOKEN(STR_ACTION_STATUS), Response);
  return Ret == TcgResultSuccess ? EFI_SUCCESS : EFI_NOT_READY;
}

/**
  Secure Erases Opal Disk.

  @param      OpalDisk       The disk need to erase data.
  @param      Password       The input password.
  @param      PassLength     The input password length.

  @retval  EFI_SUCCESS       Do the required action success.

**/
EFI_STATUS
HiiSecureErase(
  OPAL_DISK       *OpalDisk,
  const VOID      *Password,
  UINT32          PassLength
  )
{
  CHAR8                        Response[DEFAULT_RESPONSE_SIZE];
  BOOLEAN                      PasswordFailed;
  TCG_RESULT                   Ret;
  OPAL_SESSION                 AdminSpSession;

  if (PassLength == 0) {
    return EFI_DEVICE_ERROR; // return error to indicate there is an existing Password
  }

  ZeroMem(&AdminSpSession, sizeof(AdminSpSession));
  AdminSpSession.Sscp = OpalDisk->Sscp;
  AdminSpSession.MediaId = OpalDisk->MediaId;
  AdminSpSession.OpalBaseComId = OpalDisk->OpalBaseComId;

  Ret = OpalUtilSecureErase(&AdminSpSession, Password, PassLength, &PasswordFailed);
  if (Ret == TcgResultSuccess) {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "Secure Erase: Success" );
  } else {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "Secure Erase: Failure" );
  }
  HiiSetFormString(STRING_TOKEN(STR_ACTION_STATUS), Response);

  //
  // If Password failed, return invalid passowrd
  //
  if (PasswordFailed) {
    DEBUG ((DEBUG_INFO, "returning EFI_NOT_READY to indicate Password was not correct\n"));
    return EFI_NOT_READY;
  }

  //
  // Indicates Password was valid and is not changing to UEFI
  // Response string will indicate action error
  //
  return EFI_DEVICE_ERROR;
}


/**
  Disables User for Opal Disk.

  @param      OpalDisk       The disk need to the action.
  @param      Password       The input password.
  @param      PassLength     The input password length.

  @retval  EFI_SUCCESS       Do the required action success.

**/
EFI_STATUS
HiiDisableUser(
  OPAL_DISK      *OpalDisk,
  VOID           *Password,
  UINT32         PassLength
  )
{
  CHAR8                        Response[ DEFAULT_RESPONSE_SIZE ];
  BOOLEAN                      PasswordFailed;
  TCG_RESULT                   Ret;
  OPAL_SESSION                 Session;

  if (PassLength == 0) {
    return EFI_DEVICE_ERROR; // return error to indicate there is an existing Password
  }

  ZeroMem(&Session, sizeof(Session));
  Session.Sscp = OpalDisk->Sscp;
  Session.MediaId = OpalDisk->MediaId;
  Session.OpalBaseComId = OpalDisk->OpalBaseComId;

  Ret = OpalSupportDisableUser(&Session, Password, PassLength, &PasswordFailed, OpalDisk->OpalDevicePath);
  if (Ret == TcgResultSuccess) {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "Disable User: Success" );
  } else {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "Disable User: Failure" );
  }
  HiiSetFormString (STRING_TOKEN(STR_ACTION_STATUS), Response);

  //
  // If Password failed, return invalid passowrd
  //
  if (PasswordFailed) {
    DEBUG ((DEBUG_INFO, "returning EFI_NOT_READY to indicate Password was not correct\n"));
    return EFI_NOT_READY;
  }

  //
  // Indicates Password was valid and is not changing to UEFI
  // Response string will indicate action error
  //
  return EFI_DEVICE_ERROR;
}

/**
  Revert Opal Disk as Admin1.

  @param      OpalDisk       The disk need to the action.
  @param      Password       The input password.
  @param      PassLength     The input password length.
  @param      KeepUserData   Whether need to keey user data.

  @retval  EFI_SUCCESS       Do the required action success.

**/
EFI_STATUS
HiiRevert(
  OPAL_DISK       *OpalDisk,
  VOID            *Password,
  UINT32          PassLength,
  BOOLEAN         KeepUserData
  )
{
  CHAR8                         Response[ DEFAULT_RESPONSE_SIZE ];
  BOOLEAN                      PasswordFailed;
  TCG_RESULT                    Ret;
  OPAL_SESSION                  Session;

  if (PassLength == 0) {
    DEBUG ((DEBUG_INFO, "Returning error to indicate there is an existing Password\n"));
    // return error to indicate there is an existing Password
    return EFI_DEVICE_ERROR;
  }

  ZeroMem(&Session, sizeof(Session));
  Session.Sscp = OpalDisk->Sscp;
  Session.MediaId = OpalDisk->MediaId;
  Session.OpalBaseComId = OpalDisk->OpalBaseComId;

  Ret = OpalSupportRevert(
                      &Session,
                      KeepUserData,
                      Password,
                      PassLength,
                      OpalDisk->Msid,
                      OpalDisk->MsidLength,
                      &PasswordFailed,
                      OpalDisk->OpalDevicePath
                      );
  if (Ret == TcgResultSuccess) {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "Revert: Success" );
  } else {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "Revert: Failure" );
  }
  HiiSetFormString(STRING_TOKEN(STR_ACTION_STATUS), Response);

  //
  // If Password failed, return invalid passowrd
  //
  if (PasswordFailed) {
    DEBUG ((DEBUG_INFO, "returning EFI_NOT_READY to indicate Password was not correct\n"));
    return EFI_NOT_READY;
  }

  //
  // Indicates Password was valid and is not changing to UEFI
  // Response string will indicate action error
  //
  return EFI_DEVICE_ERROR;
}

/**
  Unlocks Opal Disk.

  @param      OpalDisk       The disk need to the action.
  @param      Password       The input password.
  @param      PassLength     The input password length.

  @retval  EFI_SUCCESS       Do the required action success.

**/
EFI_STATUS
HiiUnlock(
  OPAL_DISK         *OpalDisk,
  VOID              *Password,
  UINT32            PassLength
  )
{
  CHAR8                         Response[DEFAULT_RESPONSE_SIZE];
  TCG_RESULT                    Ret;
  OPAL_SESSION                  Session;

  if (PassLength == 0) {
    DEBUG ((DEBUG_INFO, "Returning error to indicate there is an existing Password\n"));
    return EFI_DEVICE_ERROR; // return error to indicate there is an existing Password
  }

  ZeroMem(&Session, sizeof(Session));
  Session.Sscp = OpalDisk->Sscp;
  Session.MediaId = OpalDisk->MediaId;
  Session.OpalBaseComId = OpalDisk->OpalBaseComId;

  Ret = OpalSupportUnlock(&Session, Password, PassLength, OpalDisk->OpalDevicePath);
  if (Ret == TcgResultSuccess) {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "Unlock: Success" );
  } else {
    AsciiSPrint( Response, DEFAULT_RESPONSE_SIZE, "%a", "Unlock: Failure" );
  }

  HiiSetFormString(STRING_TOKEN(STR_ACTION_STATUS), Response);

  if (Ret == TcgResultSuccess) {
    DEBUG ((DEBUG_INFO, "returning error to indicate Password was correct but is not changing\n"));
    return EFI_DEVICE_ERROR;
  } else {
    DEBUG ((DEBUG_INFO, "returning EFI_NOT_READY to indicate Password was not correct\n"));
    return EFI_NOT_READY;
  }
}

/**
  Use the input password to do the specified action.

  @param      Str            The input password saved in.

  @retval  EFI_SUCCESS       Do the required action success.
  @retval  Others            Other error occur.

**/
EFI_STATUS
HiiPasswordEntered(
  EFI_STRING_ID            Str
  )
{
  OPAL_DISK*                    OpalDisk;
  CHAR8                         Password[MAX_PASSWORD_CHARACTER_LENGTH + 1];
  CHAR16*                      UniStr;
  UINT32                       PassLength;
  EFI_STATUS                   Status;

  OpalHiiGetBrowserData();

  OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
  if (OpalDisk == NULL) {
    DEBUG ((DEBUG_INFO, "ERROR: disk %u not found\n", gHiiConfiguration.SelectedDiskIndex));
    return EFI_NOT_FOUND;
  }

  if (Str == 0) {
    DEBUG ((DEBUG_INFO, "ERROR: str=NULL\n"));
    return EFI_INVALID_PARAMETER;
  }

  ZeroMem(Password, sizeof(Password));

  UniStr = HiiGetString(gHiiPackageListHandle, Str, NULL);
  if (UniStr == NULL) {
    return EFI_NOT_FOUND;
  }
  PassLength = (UINT32) StrLen (UniStr);
  if (PassLength >= sizeof(Password)) {
    HiiSetFormString(STRING_TOKEN(STR_ACTION_STATUS), "Password too long");
    gBS->FreePool(UniStr);
    return EFI_BUFFER_TOO_SMALL;
  }

  UnicodeStrToAsciiStr(UniStr, Password);
  gBS->FreePool(UniStr);

  if (gHiiConfiguration.SelectedAction == HII_KEY_ID_GOTO_UNLOCK) {
    Status = HiiUnlock (OpalDisk, Password, PassLength);
  } else if (gHiiConfiguration.SelectedAction == HII_KEY_ID_GOTO_SECURE_ERASE) {
    Status = HiiSecureErase (OpalDisk, Password, PassLength);
  } else if (gHiiConfiguration.SelectedAction == HII_KEY_ID_GOTO_DISABLE_USER) {
    Status = HiiDisableUser (OpalDisk, Password, PassLength);
  } else if (gHiiConfiguration.SelectedAction == HII_KEY_ID_GOTO_REVERT) {
    DEBUG ((DEBUG_INFO, "gHiiConfiguration.KeepUserData %u\n", gHiiConfiguration.KeepUserData));
    Status = HiiRevert(OpalDisk, Password, PassLength, gHiiConfiguration.KeepUserData);
  } else {
    Status = HiiSetPassword(OpalDisk, Password, PassLength);
  }

  OpalHiiSetBrowserData ();

  return Status;
}

/**
  Update block sid info.

  @param      Enable         Enable/disable BlockSid.

  @retval  EFI_SUCCESS       Do the required action success.
  @retval  Others            Other error occur.

**/
EFI_STATUS
HiiSetBlockSid (
  BOOLEAN          Enable
  )
{
  EFI_STATUS                            Status;
  OPAL_EXTRA_INFO_VAR                   OpalExtraInfo;
  UINTN                                 DataSize;

  Status = EFI_SUCCESS;

  OpalExtraInfo.EnableBlockSid = Enable;
  DataSize = sizeof (OPAL_EXTRA_INFO_VAR);
  Status = gRT->SetVariable (
                 OPAL_EXTRA_INFO_VAR_NAME,
                 &gOpalExtraInfoVariableGuid,
                 EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                 DataSize,
                 &OpalExtraInfo
                 );

  return Status;
}

/**
  This function processes the results of changes in configuration.

  @param  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Configuration          A null-terminated Unicode string in <ConfigResp>
                                 format.
  @param  Progress               A pointer to a string filled in with the offset of
                                 the most recent '&' before the first failing
                                 name/value pair (or the beginning of the string if
                                 the failure is in the first name/value pair) or
                                 the terminating NULL if all was successful.

  @retval EFI_SUCCESS            The Results is processed successfully.
  @retval EFI_INVALID_PARAMETER  Configuration is NULL.
  @retval EFI_NOT_FOUND          Routing data doesn't match any storage in this
                                 driver.

**/
EFI_STATUS
EFIAPI
RouteConfig(
  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL    *This,
  CONST EFI_STRING                        Configuration,
  EFI_STRING                              *Progress
  )
{
  if (Configuration == NULL || Progress == NULL) {
    return (EFI_INVALID_PARAMETER);
  }

  *Progress = Configuration;
  if (!HiiIsConfigHdrMatch (Configuration, &gHiiSetupVariableGuid, OpalPasswordStorageName)) {
    return EFI_NOT_FOUND;
  }

  *Progress = Configuration + StrLen (Configuration);

  return EFI_SUCCESS;
}

/**
  This function allows a caller to extract the current configuration for one
  or more named elements from the target driver.

  @param  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Request                A null-terminated Unicode string in
                                 <ConfigRequest> format.
  @param  Progress               On return, points to a character in the Request
                                 string. Points to the string's null terminator if
                                 request was successful. Points to the most recent
                                 '&' before the first failing name/value pair (or
                                 the beginning of the string if the failure is in
                                 the first name/value pair) if the request was not
                                 successful.
  @param  Results                A null-terminated Unicode string in
                                 <ConfigAltResp> format which has all values filled
                                 in for the names in the Request string. String to
                                 be allocated by the called function.

  @retval EFI_SUCCESS            The Results is filled with the requested values.
  @retval EFI_OUT_OF_RESOURCES   Not enough memory to store the results.
  @retval EFI_INVALID_PARAMETER  Request is illegal syntax, or unknown name.
  @retval EFI_NOT_FOUND          Routing data doesn't match any storage in this
                                 driver.

**/
EFI_STATUS
EFIAPI
ExtractConfig(
  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL    *This,
  CONST EFI_STRING                        Request,
  EFI_STRING                              *Progress,
  EFI_STRING                              *Results
  )
{
  EFI_STATUS                              Status;

  //
  // Check for valid parameters
  //
  if (Progress == NULL || Results == NULL) {
    return (EFI_INVALID_PARAMETER);
  }

  *Progress = Request;
  if ((Request != NULL) &&
    !HiiIsConfigHdrMatch (Request, &gHiiSetupVariableGuid, OpalPasswordStorageName)) {
    return EFI_NOT_FOUND;
  }

  //
  // Convert Buffer Data to <ConfigResp> by helper function BlockToConfig( )
  //
  Status = gHiiConfigRouting->BlockToConfig(
               gHiiConfigRouting,
               Request,
               (UINT8*)&gHiiConfiguration,
               sizeof(OPAL_HII_CONFIGURATION),
               Results,
               Progress
           );

  return (Status);
}


/**

  Pass the current system state to the bios via the hii_G_Configuration.

**/
VOID
OpalHiiSetBrowserData (
  VOID
  )
{
  HiiSetBrowserData(
      &gHiiSetupVariableGuid,
      (CHAR16*)L"OpalHiiConfig",
      sizeof(gHiiConfiguration),
      (UINT8*)&gHiiConfiguration,
      NULL
  );
}


/**

  Populate the hii_g_Configuraton with the browser Data.

**/
VOID
OpalHiiGetBrowserData (
  VOID
  )
{
  HiiGetBrowserData(
      &gHiiSetupVariableGuid,
      (CHAR16*)L"OpalHiiConfig",
      sizeof(gHiiConfiguration),
      (UINT8*)&gHiiConfiguration
  );
}

/**
  Set a string Value in a form.

  @param      DestStringId   The stringid which need to update.
  @param      SrcAsciiStr    The string nned to update.

  @retval  EFI_SUCCESS       Do the required action success.
  @retval  Others            Other error occur.

**/
EFI_STATUS
HiiSetFormString(
  EFI_STRING_ID       DestStringId,
  CHAR8               *SrcAsciiStr
  )
{
  UINT32                    Len;
  UINT32                    UniSize;
  CHAR16*                   UniStr;

  //
  // Determine the Length of the sting
  //
  Len = ( UINT32 )AsciiStrLen( SrcAsciiStr );

  //
  // Allocate space for the unicode string, including terminator
  //
  UniSize = (Len + 1) * sizeof(CHAR16);
  UniStr = (CHAR16*)AllocateZeroPool(UniSize);

  //
  // Copy into unicode string, then copy into string id
  //
  AsciiStrToUnicodeStr( SrcAsciiStr, UniStr );

  //
  // Update the string in the form
  //
  if (HiiSetString(gHiiPackageListHandle, DestStringId, UniStr, NULL) == 0) {
    DEBUG ((DEBUG_INFO,  "HiiSetFormString( ) failed\n"));
    FreePool(UniStr);
    return (EFI_OUT_OF_RESOURCES);
  }

  //
  // Free the memory
  //
  FreePool(UniStr);

  return (EFI_SUCCESS);
}

/**
  Initialize the Opal disk base on the hardware info get from device.

  @param Dev                  The Opal device.

  @retval EFI_SUCESS          Initialize the device success.
  @retval EFI_DEVICE_ERROR    Get info from device failed.

**/
EFI_STATUS
OpalDiskInitialize (
  IN OPAL_DRIVER_DEVICE          *Dev
  )
{
  TCG_RESULT                  TcgResult;
  OPAL_SESSION                Session;

  ZeroMem(&Dev->OpalDisk, sizeof(OPAL_DISK));
  Dev->OpalDisk.Sscp = Dev->Sscp;
  Dev->OpalDisk.MediaId = Dev->MediaId;
  Dev->OpalDisk.OpalDevicePath = Dev->OpalDevicePath;

  ZeroMem(&Session, sizeof(Session));
  Session.Sscp = Dev->Sscp;
  Session.MediaId = Dev->MediaId;

  TcgResult = OpalGetSupportedAttributesInfo (&Session, &Dev->OpalDisk.SupportedAttributes, &Dev->OpalDisk.OpalBaseComId);
  if (TcgResult != TcgResultSuccess) {
    return EFI_DEVICE_ERROR;
  }
  Session.OpalBaseComId = Dev->OpalDisk.OpalBaseComId;

  TcgResult = OpalUtilGetMsid (&Session, Dev->OpalDisk.Msid, OPAL_MSID_LENGHT, &Dev->OpalDisk.MsidLength);
  if (TcgResult != TcgResultSuccess) {
    return EFI_DEVICE_ERROR;
  }

  return OpalDiskUpdateStatus (&Dev->OpalDisk);
}

/**
  Update the device info.

  @param OpalDisk                The Opal device.

  @retval EFI_SUCESS             Initialize the device success.
  @retval EFI_DEVICE_ERROR       Get info from device failed.
  @retval EFI_INVALID_PARAMETER  Not get Msid info before get ownership info.

**/
EFI_STATUS
OpalDiskUpdateStatus (
  OPAL_DISK        *OpalDisk
  )
{
  TCG_RESULT                  TcgResult;
  OPAL_SESSION                Session;

  ZeroMem(&Session, sizeof(Session));
  Session.Sscp = OpalDisk->Sscp;
  Session.MediaId = OpalDisk->MediaId;
  Session.OpalBaseComId = OpalDisk->OpalBaseComId;

  TcgResult = OpalGetLockingInfo(&Session, &OpalDisk->LockingFeature);
  if (TcgResult != TcgResultSuccess) {
    return EFI_DEVICE_ERROR;
  }

  if (OpalDisk->MsidLength == 0) {
    return EFI_INVALID_PARAMETER;
  } else {
    //
    // Base on the Msid info to get the ownership, so Msid info must get first.
    //
    OpalDisk->Owner = OpalUtilDetermineOwnership(&Session, OpalDisk->Msid, OpalDisk->MsidLength);
  }

  return EFI_SUCCESS;
}

